package org.lc.httpcollector.tool

import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import org.lc.httpcollector.authFactory
import org.lc.httpcollector.callableFactory
import org.lc.httpcollector.clientFactory
import org.lc.httpcollector.model.InnerError
import org.lc.httpcollector.model.Response
import org.lc.httpcollector.model.Server
import org.lc.httpcollector.requestFactory
import java.io.IOException
import java.security.SecureRandom
import java.security.cert.X509Certificate
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.X509TrustManager
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

object HttpUtil {
    fun defaultClientFactory(server: Server) = OkHttpClient.Builder()
        .hostnameVerifier { _, _ -> true }
        .sslSocketFactory(sslFactory, TrustAllManager)
        .connectTimeout(60L, TimeUnit.SECONDS)
        .apply { authenticator(authFactory(server)) }
        .build()

    fun defaultRequestFactory(server: Server, url: String) =
        Request.Builder()
            .url("${if (server.https) "https" else "http"}://${server.ip.formatUrlIp()}:${server.port}$url")
            .method("GET", null)
            .build()

    fun defaultCallableFactory(consumer: (response: Response) -> Unit) =
        SuspendCallback(consumer)

    fun closeClient(server: Server) {
        clientMap.remove(server.ip)?.apply {
            dispatcher.executorService.shutdown()
            connectionPool.evictAll()
            cache?.close()
        }
    }

    /**
     * 发送请求
     */
    suspend fun sendAsync(server: Server, url: String): Response {
        val client = clientMap.getOrPut(server.ip) { clientFactory(server) }
        val request = requestFactory(server, url)

        return suspendCoroutine {
            client.newCall(request).enqueue(callableFactory(it::resume))
        }
    }

    private val JSON = "application/json; charset=utf-8".toMediaType()

    private val clientMap = ConcurrentHashMap<String, OkHttpClient>()
}

/**
 * basic鉴权
 */
class BasicAuth(private val server: Server) : Authenticator {

    override fun authenticate(route: Route?, response: okhttp3.Response): Request {
        val basic = Credentials.basic(server.username, server.password)
        return response.request.newBuilder().header("Authorization", basic).build()
    }
}

/**
 * 响应回调默认实现
 * @param consumer 响应模型消费者（用于返回协程）
 */
class SuspendCallback(private val consumer: (Response) -> Unit) : Callback {

    override fun onResponse(call: Call, response: okhttp3.Response) {
        consumer(transferResponse(response))
    }

    override fun onFailure(call: Call, e: IOException) {
        consumer(Response(0, innerError = InnerError(-1, "okhttp error", e)))
    }

    private fun transferResponse(resp: okhttp3.Response) = resp.use {
        Response(resp.code, resp.headers.toMap(), resp.body?.string())
    }
}

private object TrustAllManager : X509TrustManager {

    override fun checkClientTrusted(p0: Array<out X509Certificate>?, p1: String?) = Unit

    override fun checkServerTrusted(p0: Array<out X509Certificate>?, p1: String?) = Unit
    override fun getAcceptedIssuers() = emptyArray<X509Certificate>()
}

private val sslFactory: SSLSocketFactory = SSLContext.getInstance("SSL").apply {
    init(null, arrayOf(TrustAllManager), SecureRandom())
}.socketFactory

private fun String.formatUrlIp() = if (this.contains(":")) "[$this]" else this